/*
 * Copyright © 2017 eqxiu.com 北京中网易企秀科技有限公司  All rights reserved.
 */
package cn.hermit.data.json;

import cn.hermit.util.StringUtils;

/**
 * Provides various of tools for handling characters in JSON string.
 * 
 * 
 * @author Jack Gao (Chinese name : GAO JIANGUO, Email : linux.gjg@gmail.com)
 * @date 18 Jun, 2014
 */
public class JSONChar {
	/**
	 * Escape JSON string with assumption that the string has been escaped partly
	 * 
	 * @see #escape(String, boolean, boolean)
	 * @param text
	 * 		Plain text
	 * @return
	 * 		Escaped JSON string
	 */
	public static String escape(String text) {
		return escape(text, false, true);
	}

	/**
	 * Escape JSON string with assumption that the string has been escaped partly
	 * 
	 * @see #escape(String, boolean, boolean)
	 * 
	 * @param text
	 * 		Plain text
	 * @param forwardSlashEscaping
	 * 		Switch for forward slash "/" escaping
	 * 		<p>
	 * 		 If true, will escape forward slashes {@code // => \/\/}.
	 *       Otherwise, will escape forward slashes {@code // => //}
	 *       </p>	
	 * @return
	 * 		Escaped JSON string
	 */
	public static String escape(String text, boolean forwardSlashEscaping) {
		return escape(text, forwardSlashEscaping, true);
	}

	/**
	 * Escape quotes, \, /, \r, \n, \b, \f, \t and other control characters
	 * (U+0000 through U+001F) from plain text to JSON string.
	 * 
	 * @param text
	 * 		Plain text
	 * @param forwardSlashEscaping
	 * 		Switch for forward slash "/" escaping
	 * 		<p>
	 * 		 If true, will escape forward slashes {@code // => \/\/}
	 *       Otherwise, will escape forward slashes {@code // => //}
	 *       </p>
	 * @param hasPartEscaping
	 * 		Assumption of if plain text has been escaped partly
	 * @return
	 * 		Escaped JSON string
	 */
	public static String escape(String text, boolean forwardSlashEscaping,
			boolean hasPartEscaping) {
		if (text == null || text.length() == 0) {
			return text;
		}
		StringBuilder sb = new StringBuilder();
		int len = text.length();
		for (int i = 0; i < len; i++) {
			char ch = text.charAt(i);
			if (ch == '\\') {
				if (i == len - 1) {
					sb.append("\\\\");
				} else if (i < len - 1) {
					char c = text.charAt(++i);
					if ((c == 'x' || c == 'X') && (i < len - 2)) {
						String left = text.substring(i + 1, i + 3);
						try {
							Integer.parseInt(left, 16);
							sb.append("\\").append(c).append(left);
							i = i + 2;
						} catch (NumberFormatException e) {
							sb.append("\\\\").append(c).append(left);
						}
					} else if ((c == 'u' || c == 'U') && (i < len - 4)) {
						String left = text.substring(i + 1, i + 5);
						try {
							Integer.parseInt(left, 16);
							sb.append("\\").append(c).append(left);
							i = i + 4;
						} catch (NumberFormatException e) {
							sb.append("\\\\").append(c).append(left);
						}
					} else if (hasPartEscaping
							&& (c == '"' || c == '\\' || c == '/' || c == 'b'
									|| c == 'f' || c == 'n' || c == 'r' || c == 't')) {
						if (c == '/' && !forwardSlashEscaping) {
							sb.append("\\\\");
							i--;
						} else {
							sb.append("\\").append(c);
						}
					} else {
						sb.append("\\\\");
						i--;
					}
				}
			} else {
				switch (ch) {
				case '"':
					sb.append("\\\"");
					break;
				case '\\':
					i--;
					break;
				case '/':
					if (forwardSlashEscaping) {
						sb.append("\\/");
					} else {
						sb.append(ch);
					}
					break;
				case '\b':
					sb.append("\\b");
					break;
				case '\f':
					sb.append("\\f");
					break;
				case '\n': {
					sb.append("\\n");
					break;
				}
				case '\r': {
					sb.append("\\r");
					break;
				}
				case '\t':
					sb.append("\\t");
					break;
				default:
					// Reference: http://www.unicode.org/versions/Unicode5.1.0/
					// C0 Controls 0000-001F
					// C1 Controls 0080-009F
					// Superscripts and Subscripts 2070-209F
					// Currency Symbols 20A0-20CF
					// Combining Diacritics Marks for Symbols 20D0-20FF
					if ((ch >= '\u0000' && ch <= '\u001F')
							|| (ch >= '\u007F' && ch <= '\u009F')
							|| (ch >= '\u2000' && ch <= '\u20FF')) {
						sb.append(StringUtils.valueOfUnicode(ch));
					} else {
						sb.append(ch);
					}
				}
			}
		}
		return sb.toString();
	}

	/**
	 * UN-escape from JSON string to plain text
	 * @see #escape(String, boolean, boolean) escape method
	 * 
	 * @param value
	 * 		JSON string
	 * @param forwardSlashEscaping
	 * 		If true, will consider the escaping for forward slashes
	 * @return
	 * 		Plain text
	 */
	public static String unescape(String value, boolean forwardSlashEscaping) {
		if (value == null || "".equals(value.trim())) {
			return value;
		}
		StringBuilder sb = new StringBuilder();
		int len = value.length();
		for (int i = 0; i < len; i++) {
			char ch = value.charAt(i);
			if ('\\' == ch) {
				if (i < len - 1) {
					char nextCh = value.charAt(++i);
					if ('u' == nextCh) {
						sb.append(ch).append(nextCh);
					} else {
						switch (nextCh) {
						case '"':
							sb.append('"');
							break;
						case '\\':
							sb.append('\\');
							break;
						case '/':
							if (forwardSlashEscaping) {
								sb.append('/');
							} else {
								sb.append("\\/");
							}
							break;
						case 'b':
							sb.append('\b');
							break;
						case 'f':
							sb.append('\f');
							break;
						case 'n':
							sb.append('\n');
							break;
						case 'r':
							sb.append('\r');
							break;
						case 't':
//							sb.append('\t');
							sb.append("  ");
							break;
						default:
							sb.append(ch).append(nextCh);// avoid impossible
							break;
						}
					}
				} else {
					sb.append(ch);// avoid impossible
				}
			} else {
				sb.append(ch);
			}
		}
		return sb.toString();
	}

}